/**
 * Copyright © 2016-2022 The Thingsboard Authors
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.thingsboard.server.dao.iotconfig;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
import org.thingsboard.server.common.data.IotConfig;
import org.thingsboard.server.common.data.id.IotConfigId;

import java.io.IOException;
import java.util.Optional;

import static org.thingsboard.server.dao.service.Validator.validateId;

@Slf4j
@Service
public class IotConfigServiceImpl extends AbstractEntityService implements IotConfigService {

    public static final String PUBLIC_IOTCONFIG_TITLE = "Public";
    public static final String INCORRECT_IOTCONFIG_ID = "Incorrect iotConfigId ";
    public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";

    @Autowired
    private IotConfigDao iotConfigDao;

//    @Autowired
//    private UserService userService;
//
//    @Autowired
//    private AssetService assetService;
//
//    @Autowired
//    private DeviceService deviceService;
//
//    @Autowired
//    private EntityViewService entityViewService;
//
//    @Autowired
//    private DashboardService dashboardService;

    @Autowired
    private ApiUsageStateService apiUsageStateService;

    @Autowired
    private DataValidator<IotConfig> iotConfigValidator;


    @Override
    public IotConfig findIotConfigById(TenantId tenantId, IotConfigId iotConfigId) {
        log.trace("Executing findIotConfigById [{}]", iotConfigId);
        Validator.validateId(iotConfigId, INCORRECT_IOTCONFIG_ID + iotConfigId);
        return iotConfigDao.findById(tenantId, iotConfigId.getId());
    }

    @Override
    public Optional<IotConfig> findIotConfigByTenantIdAndTitle(TenantId tenantId, String title) {
        log.trace("Executing findIotConfigByTenantIdAndTitle [{}] [{}]", tenantId, title);
        validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
        return iotConfigDao.findIotConfigsByTenantIdAndTitle(tenantId.getId(), title);
    }

    @Override
    public ListenableFuture<IotConfig> findIotConfigByIdAsync(TenantId tenantId, IotConfigId iotConfigId) {
        log.trace("Executing findIotConfigByIdAsync [{}]", iotConfigId);
        validateId(iotConfigId, INCORRECT_IOTCONFIG_ID + iotConfigId);
        return iotConfigDao.findByIdAsync(tenantId, iotConfigId.getId());
    }

    @Override
    public IotConfig saveIotConfig(IotConfig iotConfig) {
        log.trace("Executing saveIotConfig [{}]", iotConfig);
        iotConfigValidator.validate(iotConfig, IotConfig::getTenantId);
        IotConfig savedIotConfig = iotConfigDao.save(iotConfig.getTenantId(), iotConfig);
        // dashboardService.updateCustomerDashboards(savedIotConfig.getTenantId(), savedIotConfig.getId());
        return savedIotConfig;
    }

    @Override
    public void deleteIotConfig(TenantId tenantId, IotConfigId iotConfigId) {
        log.trace("Executing deleteIotConfig [{}]", iotConfigId);
        Validator.validateId(iotConfigId, INCORRECT_IOTCONFIG_ID + iotConfigId);
        IotConfig iotConfig = findIotConfigById(tenantId, iotConfigId);
        if (iotConfig == null) {
            throw new IncorrectParameterException("Unable to delete non-existent iotConfig.");
        }
//        dashboardService.unassignCustomerDashboards(tenantId, iotConfigId);
//        entityViewService.unassignCustomerEntityViews(iotConfig.getTenantId(), iotConfigId);
//        assetService.unassignCustomerAssets(iotConfig.getTenantId(), iotConfigId);
//        deviceService.unassignCustomerDevices(iotConfig.getTenantId(), iotConfigId);
//        edgeService.unassignCustomerEdges(iotConfig.getTenantId(), iotConfigId);
//        userService.deleteCustomerUsers(iotConfig.getTenantId(), iotConfigId);
        deleteEntityRelations(tenantId, iotConfigId);
        apiUsageStateService.deleteApiUsageStateByEntityId(iotConfigId);
        iotConfigDao.removeById(tenantId, iotConfigId.getId());
    }

    @Override
    public IotConfig findOrCreatePublicIotConfig(TenantId tenantId) {
        log.trace("Executing findOrCreatePublicIotConfig, tenantId [{}]", tenantId);
        Validator.validateId(tenantId, INCORRECT_IOTCONFIG_ID + tenantId);
        Optional<IotConfig> publicIotConfigOpt = iotConfigDao.findIotConfigsByTenantIdAndTitle(tenantId.getId(), PUBLIC_IOTCONFIG_TITLE);
        if (publicIotConfigOpt.isPresent()) {
            return publicIotConfigOpt.get();
        } else {
            IotConfig publicIotConfig = new IotConfig();
            publicIotConfig.setTenantId(tenantId);
            publicIotConfig.setTitle(PUBLIC_IOTCONFIG_TITLE);
            try {
                publicIotConfig.setAdditionalInfo(new ObjectMapper().readValue("{ \"isPublic\": true }", JsonNode.class));
            } catch (IOException e) {
                throw new IncorrectParameterException("Unable to create public iotConfig.", e);
            }
            return iotConfigDao.save(tenantId, publicIotConfig);
        }
    }

    @Override
    public PageData<IotConfig> findIotConfigsByTenantId(TenantId tenantId, PageLink pageLink) {
        log.trace("Executing findIotConfigsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
        Validator.validatePageLink(pageLink);
        return iotConfigDao.findIotConfigsByTenantId(tenantId.getId(), pageLink);
    }

    @Override
    public void deleteIotConfigsByTenantId(TenantId tenantId) {
        log.trace("Executing deleteIotConfigsByTenantId, tenantId [{}]", tenantId);
        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
        iotConfigsByTenantRemover.removeEntities(tenantId, tenantId);
    }

    private PaginatedRemover<TenantId, IotConfig> iotConfigsByTenantRemover =
            new PaginatedRemover<TenantId, IotConfig>() {

                @Override
                protected PageData<IotConfig> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
                    return iotConfigDao.findIotConfigsByTenantId(id.getId(), pageLink);
                }

                @Override
                protected void removeEntity(TenantId tenantId, IotConfig entity) {
                    deleteIotConfig(tenantId, new IotConfigId(entity.getUuidId()));
                }
            };
}
